using System;
using Glue.Converters;
using Glue.Internals.Nulls;

namespace Glue.Internals
{
    internal class CreatorsForCollectionMembers
    {
        private readonly ICollectionMember leftProperty;
        private readonly IConverter converter;
        private readonly ICollectionMember rightProperty;
        private readonly RelationDirection direction;

        private readonly Func<object, object> dontCallMeCreator =
            x => { throw new GlueException("System error (Contact Glue developers): CollectionMapping tries to access an unset creator"); };

        public CreatorsForCollectionMembers(ICollectionMember leftProperty, ICollectionMember rightProperty, IConverter converter, RelationDirection direction)
        {
            this.leftProperty = leftProperty;
            this.converter = converter;
            this.rightProperty = rightProperty;
            this.direction = direction;
        }

        public CreatorContainer AssertAndGetCreators()
        {
            
            var nestedConverterMaybe = (converter as IHoldNestedRelation).AsMaybe();
            if (!nestedConverterMaybe.HasValue())
            {
                return GetPrimitiveCreators();
            }

            var container = new CreatorContainer();

            var nestedConverter = nestedConverterMaybe.GetValue();

            AssertAndSetCreatorFromNestedConverter(
                RelationDirection.TowardsRight, direction, nestedConverter.GetCreatorTowardsRight(), rightProperty.GetElementType(),
                GetErrorMessage(nestedConverter, leftProperty, rightProperty, "TowardsRight"), container);
            AssertAndSetCreatorFromNestedConverter(
                RelationDirection.TowardsLeft, direction, nestedConverter.GetCreatorTowardsLeft(), leftProperty.GetElementType(),
                GetErrorMessage(nestedConverter, leftProperty, rightProperty, "TowardsLeft"), container);

            return container;
        }

        private CreatorContainer GetPrimitiveCreators()
        {
            var bothCreators = new CreatorContainer();
            bothCreators.Add(leftProperty.GetElementType(), x => Primitives.CreateInstance(leftProperty.GetElementType()));
            if (!bothCreators.Contains(rightProperty.GetElementType()))
                bothCreators.Add(rightProperty.GetElementType(), x => Primitives.CreateInstance(rightProperty.GetElementType()));
            return bothCreators;
        }

        private void AssertAndSetCreatorFromNestedConverter(RelationDirection directionToAssertAgainst, RelationDirection actualDirection,
                                                            Maybe<Func<object, object>> creator, Type elementType, String errorMessage, CreatorContainer bothCreators)
        {
            if (actualDirection == RelationDirection.TwoWays || actualDirection == directionToAssertAgainst)
            {
                if (!creator.HasValue())
                    throw new GlueException(errorMessage);

                bothCreators.Add(elementType, creator.GetValue());
            }
            else
            {
                bothCreators.Add(elementType, dontCallMeCreator);
            }
        }

        private String GetErrorMessage(IHoldNestedRelation nestedConverter, IObjectMember leftProperty, IObjectMember rightProperty, string creatorMissingTowards)
        {
            return "A nested mapping between '" + nestedConverter.LeftType.Name + "' and '" + nestedConverter.RightType.Name + "' has been added to a relation between '" + leftProperty.Name + "' and '" + rightProperty.Name + "' (a list relation), where that mapping does not have a creator " + creatorMissingTowards + ". Add a creator " + creatorMissingTowards + " when creating mapping between '" + nestedConverter.LeftType.Name + "' and '" + nestedConverter.RightType.Name + "'";
        }
    }
}